#include "ET++.ph"
#ifdef __GNUG__
#pragma implementation
#endif

#include "Inspector.h"

#include "Class.h"
#include "ObjList.h"
#include "OrdColl.h"
#include "Dictionary.h"
#include "ObjectTable.h"
#include "ClassItem.h"
#include "ClassManager.h"
#include "AccessMem.h"
#include "MenuBar.h"
#include "WindowSystem.h"
#include "Buttons.h"
#include "Filler.h"
#include "Math.h"
#include "Fields.h"
#include "Menu.h"
#include "Scroller.h"
#include "Alert_e.h"
#include "Box.h"
#include "String.h"

extern Object *gApplication;

#include "ObjectView.h"
#include "Reference.h"
#include "InspItem.h"
#include "ClassList.h"
#include "EtPeManager.h"
#include "EtPeCmdNo.h"
#include "EtProgEnv.h"

const int cMaxObjects= 400;

//---- update References to deleted objects

void InspectorFreeHook(void*, void *addr, size_t sz)
{
	void *end= (void*)((u_long)addr + sz);
	for (int i= 0; i < PeInspector::allInspectors->Size(); i++) {
		PeInspector *ip= (PeInspector*)PeInspector::allInspectors->At(i);
		if (addr != ip)
			ip->FreedMemory(addr, end);
	}
}

//---- FindInspReferences ------------------------------------------------------

class PeFindInspReferences: public AccessObjPtrs {
protected:
	Object *referencesTo;
	PeInspector *ip;
public:
	PeFindInspReferences(PeInspector *insp) : AccessObjPtrs(0)
		{ ip= insp; }
	void ReferencesTo(Object *op)
		{ referencesTo= op; }
	void FoundPointer(Object*, char*, int, bool);
};

void PeFindInspReferences::FoundPointer(Object *op, char *name, int at, bool)
{
	if (op == referencesTo /* &&  ?? !inObject->IsKindOf(Assoc) */
											&& !inObject->IsKindOf(PeObjectItem))
		ip->FoundReference(inObject, name, at);
}

//---- Inspector ---------------------------------------------------------------

NewMetaImpl(PeInspector, EtPeTool, (TP(classes), TP(objects), TP(classItems),
		TP(objectItems), TP(references), TP(referenceItems), TP(refTitle),
		TP(objTitle)));

PeInspector *PeInspector::inspector= 0;
int PeInspector::inspcnt= 0;
OrdCollection *PeInspector::allInspectors= 0;

PeInspector::PeInspector(Manager *m) : EtPeTool(m, "Inspector")
{
}

PeInspector::~PeInspector()
{
	allInspectors->RemovePtr(this);
	if (allInspectors->Size() == 0) {
		Storage::SetFreeHook(0, 0);
		SafeDelete(allInspectors);
	}
	SafeDelete(objView);
	SafeDelete(findReferences);
	SafeDelete(classes);
	SafeDelete(objects);
	SafeDelete(references);
	if (path) {
		path->FreeAll();
		SafeDelete(path);
	}
}

VObject *PeInspector::DoMakeContent()
{
	findReferences= new PeFindInspReferences(this);

	classes= new PeClassListView(this, TRUE);

	objTitle= new TextField(cIdNone, 20, gFixedFont->WithFace(eFaceItalic));
	objTitle->SetEditable(FALSE);
	objects= new CollectionView(this, 0, eCVDefault);
	objects->SetMinExtent(Point(400, 0));
	ResetObjects();
	objects->SetId(cIdObjects);

	refTitle= new TextField(cIdNone, 20, gFixedFont->WithFace(eFaceItalic));
	refTitle->SetEditable(FALSE);
	references= new CollectionView(this, 0, eCVDefault, 0);
	references->SetMinExtent(Point(400, 0));
	ResetReferences();
	references->SetId(cIdReferences);

	objView= new PeObjectView();
	objView->SetNextHandler(this);
	SetFirstHandler(objView);

	VObject *classScroller= new Scroller(classes, Point(-1, 100));
	classScroller->SetFlag(eVObjHFixed);
	AddKeySelectItem(classScroller);

	VObject *w=
		new VBox(
			new HBox(
				classScroller,
				new VExpander(gPoint2,
					objTitle,
					new Scroller(objects, Point(150, 100), cIdNone, eScrollRight),
					0
				),
				new SplitHandle,
				new VExpander(gPoint2,
					refTitle,
					new Scroller(references, Point(150, 100), cIdNone, eScrollRight),
					0
				),
				0
			),
			new HBox(gPoint8, (VObjAlign)(eVObjHEqual|eVObjHExpand|eVObjVTop),
				start= new ActionButton(cIdShiftStart, "||<"),
				left= new ActionButton(cIdShiftLeft,  "<<"),
					new ActionButton(cIdShiftAppl,  "Appl"),
				right= new ActionButton(cIdShiftRight, ">>"),
				end= new ActionButton(cIdShiftEnd,   ">||"),
				0
			),
			new SplitHandle,
			new Scroller(objView, Point(550, 300)),
			0
		);

	path= new OrdCollection;
	if (allInspectors == 0) {
		allInspectors= new OrdCollection;
		Storage::SetFreeHook(InspectorFreeHook, 0);
	}
	allInspectors->Add(this);

	UpdateButtons();

	position= -1;

	return w;
}

MenuBar *PeInspector::DoMakeMenuBar()
{
	MenuBar *mb= EtPeTool::DoMakeMenuBar();

	Menu *m= new Menu("Classes");
	m->AppendItems( "Update List",          cUPDATELIST,
					"Hide Empty",           cEMPTYCLASSES,
					"Edit Definition",      cEDITDECL,
					"Edit Implementation",  cEDITIMPL,
					0);
	mb->AddMenu(m);

	m= new Menu("Objects");
	m->AppendItems( "All Instances",        cSHOWALL,
					"References",           cOBJREFERENCES,
					"Object View",          cABSTRVIEW,
					"Object Structure",     cOBJBROWSER,
					0);
	mb->AddMenu(m);

	return mb;
}

void PeInspector::Init(Object *op)
{
	Push(new Ref(*op));
}

void PeInspector::MoveTo(int pos)
{
	pos= Math::Range(0, path->Size() - 1, pos);
	if (pos == position)
		return;
	position= pos;
	Ref *r= (Ref*)path->At(position);
	objView->SetInspected(r);
	ShowReference(r);
	UpdateButtons();
}

void PeInspector::Push(Ref *r)
{
	if (position != path->Size() - 1)
		while(path->Size() > position+1) {
			Object *tmp= path->RemoveLast();
			if (tmp)
				delete tmp;
		}

	int i= path->IndexOf(r);

	if (i < 0)
		path->Add(new Ref(*r));
	else {
		while(path->Size() > i+1) {
			Object *tmp= path->RemoveLast();
			if (tmp)
				delete tmp;
		}
	}

	objView->SetInspected(r);
	ShowReference(r);
	position= path->Size() - 1;
	UpdateButtons();
}

void PeInspector::ShowReference(Ref *ref)
{
	Class *cl= 0;
	Object *op;

	if (op= ref->GetObject())
		cl= op->IsA();
	else {
		Reset();
		return;
	}
	ShowClass(cl);
	ShowObject((Object*)ref->GetBase());
}

void PeInspector::ShowClass(Class *cl)
{
	if (classes->SelectClass(cl))
		LoadObjectsOfClass(cl, TRUE);
}

void PeInspector::ShowObject(Object *op)
{
	PeObjectItem *oi;
	Iter next(objectItems);

	for (int i= 0; oi= (PeObjectItem*)next(); i++) {
		if (oi->GetObject() == (Object*)op) {
			SelectAndRevealItem(objects, i);
			return;
		}
	}
	objects->ClearSelection();
}

void PeInspector::SelectAndRevealItem(CollectionView *cv, int at)
{
	Rectangle sr(0, at, 1, 1);
	cv->SetSelection(sr);
	Rectangle r(cv->ItemRect(sr));
	//r.extent.x= 1;  // reveal left most pixels
	//cv->RevealRect(r, r.extent);
	cv->RevealAlign(r);
}

void PeInspector::UpdateButtons()
{
	start->Enable(position != 0);
	left->Enable(position != 0);
	bool b= position == path->Size() - 1;
	right->Enable(!b);
	end->Enable(!b);
}

void PeInspector::LoadObjectsOfClass(Class *cl, bool members)
{
	int i;
	objectItems= new ObjList;

	{
		Object *op;
		ObjectTableIter next(cl, members);

		for (i= 0; op= next(); i++) {
			objectItems->Add(new PeObjectItem(cIdNone, op));
			if (i == cMaxObjects) {
				VObject *vop= new TextItem("...", gFixedFont->WithFace(eFaceItalic));
				vop->Disable();
				objectItems->Add(vop);
				break;
			}
		}
	}
	objTitle->SetFString(TRUE, "%d instance%s", i, (i > 1) ? "s" : "");
	objects->SetCollection(objectItems, TRUE);
	objects->Update();
}

void PeInspector::Control(int id, int what, void *data)
{
	PeObjectItem *oi;
	Rectangle r;

	switch (id) {

	case cIdShiftAppl:
		Push(new Ref(*gApplication));
		break;

	case cIdShiftLeft:
		MoveTo(position-1);
		break;
		
	case cIdShiftRight:
		MoveTo(position+1);
		break;

	case cIdShiftStart:
		MoveTo(0);
		break;
		
	case cIdShiftEnd:
		MoveTo(path->Size()-1);
		break;

	case cIdObjects:
		r= objects->GetSelection();
		if (r.IsEmpty())
			return;
		oi= (PeObjectItem*) objectItems->At(r.origin.y);
		
		if (oi == 0 || !oi->IsKindOf(PeObjectItem))
			return;
		DoInspect(oi->GetObject());
		references->ClearSelection();
		break;

	case cIdReferences:
		r= references->GetSelection();
		if (r.IsEmpty())
			return;
		oi= (PeObjectItem*) referenceItems->At(r.origin.y);
		
		if (oi == 0 || !oi->IsKindOf(PeObjectItem))
			return;
		DoInspect(oi->GetObject());
		break;

	default:
		switch(what) {
		case cPeCLChangedClass:
			LoadObjectsOfClass((Class*)data, TRUE);
			references->ClearSelection();
			break;
		case cPeLoadRef:
			Push((Ref*)data);
			break;
		case cPeLoadRefNew:
			Control(cPeInspector, cPeSpawnInsp, (Ref*)data);
			break;
		}
		EtPeTool::Control(id, what, data);
		break;
	}
}

void PeInspector::DoInspect(Object *op)
{
	if (!ObjectTable::PtrIsValid(op))
		ShowAlert(eAlertNote, "instance 0x%x disappeard", op);
	else if (strcmp(op->ClassName(), "InspectorItem") == 0) // hack
		ShowAlert(eAlertNote, "would crash the inspector");
	else
		Push(new Ref(*op));
}

void PeInspector::ResetReferences()
{
	referenceItems= new OrdCollection(10);
	refTitle->SetString("References", TRUE);
	references->SetCollection(referenceItems, TRUE);
}

void PeInspector::ResetObjects()
{
	objTitle->SetString("Number of instances", TRUE);
	objectItems= new ObjList();
	objects->SetCollection(objectItems, TRUE);
}

void PeInspector::Reset()
{
	objects->ClearSelection();
	classes->ClearSelection();
}

void PeInspector::References(Object *op)
{
	referenceItems= new OrdCollection(10);
	refTitle->SetString(form("References to 0x%x (%s)", (int)op, op->ClassName()), TRUE);
	findReferences->ReferencesTo(op);
	{
		ObjectTableIter next(0);
		while (currentOp= next()) {
			findReferences->ForObject(currentOp);
			currentOp->IsA()->EnumerateMembers(findReferences);
		}
	}
	references->SetCollection(referenceItems, TRUE);
}

void PeInspector::FoundReference(Object *op, const char *name, int index)
{
	char *cp;
	if (index == -1)
		cp= form("%s.%s", op->ClassName(), name);
	else
		cp= form("%s.%s[%d]", op->ClassName(), name, index);
	referenceItems->Add(new PeObjectItem(cp, op));
}

Command *PeInspector::DoMenuCommand(int cmd)
{
	Object *insp= objView->GetInspected()->GetObject();

	switch (cmd) {

	case cSHOWALL:
		ShowAllInstances();
		break;

	case cUPDATELIST:
		classes->LoadClasses();
		ResetObjects();
		break;

	case cEMPTYCLASSES:
		classes->ToggleHideEmpty();
		ResetObjects();
		break;

	case cOBJREFERENCES:
		References(insp);
		break;
		
	case cPRINT:
		objView->Print();
		break;

	case cSPAWN:
		Control(cPeInspector, cPeSpawnInsp, objView->GetInspected());
		break;

	case cEDITDECL:
	case cEDITIMPL:
		insp->EditSource(cmd == cEDITDECL);
		break;
		
	case cOBJBROWSER:
		Control(cPeInspector, cPeShowOBrowser, insp);
		break;
	default:
		return EtPeTool::DoMenuCommand(cmd);
	}
	return gNoChanges;
}

void PeInspector::ShowAllInstances()
{
	Class *cl= classes->SelectedClass();
	if (cl != 0)
		LoadObjectsOfClass(cl, FALSE);
}

void PeInspector::DoSetupMenu(Menu *menu)
{
	EtPeTool::DoSetupMenu(menu);
	Class *cl= classes->SelectedClass();
	if (cl) {
		menu->EnableItems(cSHOWALL, cEDITDECL, cEDITIMPL, 0);
		menu->ReplaceItem(cEDITDECL, form("Edit Definition of \"%s\"", cl->Name()));
		menu->ReplaceItem(cEDITIMPL, form("Edit Implementation of \"%s\"", cl->Name()));
	}
	menu->ToggleItem(cEMPTYCLASSES, classes->HideEmptyClasses(),
						"Show All Classes", "Hide Classes with no Instances");
	menu->EnableItems(cUPDATELIST, cEMPTYCLASSES, cPRINT, 0);
}

void PeInspector::FreedMemory(void *addr, void *end)
{
	Ref *r= objView->GetInspected();
	u_int base;
	if (r) {
		base= (u_int)r->Addr();
		if (base >= (u_int)addr && base < (u_int)end)
			objView->InspectedDied();
	}
	if (path->Size() == 0)
		return;
	for (int i= 0; i < path->Size(); i++) {
		r= (Ref*)path->At(i);
		base= (u_int)r->Addr();
		if (base >= (u_int)addr && base < (u_int)end)
			RemoveDeletedRef(i, r);
	}
}
		
void PeInspector::RemoveDeletedRef(int i, Ref *r)
{
	if (i <= position)
		position= Math::Max(0, position-1);
	path->RemovePtr(r);
	UpdateButtons();
}

void PeInspector::Spawn(Manager *m, Ref *ref, bool)
{
	PeInspector *ip= new PeInspector(m);
	ip->SetOnDismiss(eMgrClose);
	ip->Show();
	ip->Push(ref);
}

void PeInspector::ShowObject(Manager *m, Object *op, bool)
{
	if (inspector == 0)
		inspector= new PeInspector(m);
	inspector->Show();
	inspector->Push(new Ref(*op));
}


